// 版权所有 Epic Games, Inc. 保留所有权利。

#pragma once

#include "Engine/AssetManager.h"
#include "LyraAssetManagerStartupJob.h"
#include "Templates/SubclassOf.h"
#include "LyraAssetManager.generated.h"

#define UE_API LYRAGAME_API

class UPrimaryDataAsset;

class ULyraGameData;
class ULyraPawnData;

struct FLyraBundles
{
	static const FName Equipped; // 装备包名称
};


/**
 * ULyraAssetManager
 *
 *	资源管理器的游戏实现，覆盖功能并存储游戏特定类型。
 *	预计大多数游戏都会想要覆盖AssetManager，因为它为游戏特定的加载逻辑提供了一个好地方。
 *	通过在DefaultEngine.ini中设置'AssetManagerClassName'来使用此类。
 */
UCLASS(MinimalAPI, Config = Game)
class ULyraAssetManager : public UAssetManager
{
	GENERATED_BODY()

public:

	UE_API ULyraAssetManager();

	// 返回AssetManager单例对象。
	static UE_API ULyraAssetManager& Get();

	// 返回由TSoftObjectPtr引用的资源。如果资源尚未加载，这将同步加载资源。
	template<typename AssetType>
	static AssetType* GetAsset(const TSoftObjectPtr<AssetType>& AssetPointer, bool bKeepInMemory = true);

	// 返回由TSoftClassPtr引用的子类。如果资源尚未加载，这将同步加载资源。
	template<typename AssetType>
	static TSubclassOf<AssetType> GetSubclass(const TSoftClassPtr<AssetType>& AssetPointer, bool bKeepInMemory = true);

	// 记录当前由资源管理器加载和跟踪的所有资源。
	static UE_API void DumpLoadedAssets();

	UE_API const ULyraGameData& GetGameData();
	UE_API const ULyraPawnData* GetDefaultPawnData() const;

protected:
	template <typename GameDataClass>
	const GameDataClass& GetOrLoadTypedGameData(const TSoftObjectPtr<GameDataClass>& DataPath)
	{
		if (TObjectPtr<UPrimaryDataAsset> const * pResult = GameDataMap.Find(GameDataClass::StaticClass())) // 在映射中查找
		{
			return *CastChecked<GameDataClass>(*pResult); // 返回找到的资源
		}

		// 如果需要，执行阻塞加载
		return *CastChecked<const GameDataClass>(LoadGameDataOfClass(GameDataClass::StaticClass(), DataPath, GameDataClass::StaticClass()->GetFName()));
	}


	static UE_API UObject* SynchronousLoadAsset(const FSoftObjectPath& AssetPath);
	static UE_API bool ShouldLogAssetLoads();

	// 线程安全的方式添加已加载的资源以保持在内存中。
	UE_API void AddLoadedAsset(const UObject* Asset);

	//~UAssetManager 接口
	UE_API virtual void StartInitialLoading() override;
#if WITH_EDITOR
	UE_API virtual void PreBeginPIE(bool bStartSimulate) override;
#endif
	//~UAssetManager 接口结束

	UE_API UPrimaryDataAsset* LoadGameDataOfClass(TSubclassOf<UPrimaryDataAsset> DataClass, const TSoftObjectPtr<UPrimaryDataAsset>& DataClassPath, FPrimaryAssetType PrimaryAssetType);

protected:

	// 要使用的全局游戏数据资源。
	UPROPERTY(Config)
	TSoftObjectPtr<ULyraGameData> LyraGameDataPath; // Lyra游戏数据路径

	// 游戏数据的加载版本
	UPROPERTY(Transient)
	TMap<TObjectPtr<UClass>, TObjectPtr<UPrimaryDataAsset>> GameDataMap; // 游戏数据映射

	// 当玩家状态上没有设置时，生成玩家棋子时使用的棋子数据。
	UPROPERTY(Config)
	TSoftObjectPtr<ULyraPawnData> DefaultPawnData; // 默认棋子数据

private:
	// 刷新StartupJobs数组。处理所有启动工作。
	UE_API void DoAllStartupJobs();

	// 设置能力系统
	UE_API void InitializeGameplayCueManager();

	// 在加载期间定期调用，可用于将状态反馈给加载屏幕
	UE_API void UpdateInitialGameContentLoadPercent(float GameContentPercent);

	// 启动时要执行的任务列表。用于跟踪启动进度。
	TArray<FLyraAssetManagerStartupJob> StartupJobs; // 启动任务数组

private:
	
	// 由资源管理器加载和跟踪的资源。
	UPROPERTY()
	TSet<TObjectPtr<const UObject>> LoadedAssets; // 已加载资源集合

	// 修改加载资源列表时用于范围锁。
	FCriticalSection LoadedAssetsCritical; // 已加载资源临界区
};


template<typename AssetType>
AssetType* ULyraAssetManager::GetAsset(const TSoftObjectPtr<AssetType>& AssetPointer, bool bKeepInMemory)
{
	AssetType* LoadedAsset = nullptr; // 初始化加载资源为空

	const FSoftObjectPath& AssetPath = AssetPointer.ToSoftObjectPath(); // 获取资源路径

	if (AssetPath.IsValid()) // 检查资源路径是否有效
	{
		LoadedAsset = AssetPointer.Get(); // 尝试获取资源
		if (!LoadedAsset) // 如果资源未加载
		{
			LoadedAsset = Cast<AssetType>(SynchronousLoadAsset(AssetPath)); // 同步加载资源
			ensureAlwaysMsgf(LoadedAsset, TEXT("加载资源失败 [%s]"), *AssetPointer.ToString()); // 确保资源加载成功
		}

		if (LoadedAsset && bKeepInMemory) // 如果资源已加载且需要保持在内存中
		{
			// 添加到已加载资源列表。
			Get().AddLoadedAsset(Cast<UObject>(LoadedAsset)); // 添加到已加载资源集合
		}
	}

	return LoadedAsset; // 返回加载的资源
}

template<typename AssetType>
TSubclassOf<AssetType> ULyraAssetManager::GetSubclass(const TSoftClassPtr<AssetType>& AssetPointer, bool bKeepInMemory)
{
	TSubclassOf<AssetType> LoadedSubclass; // 初始化加载子类

	const FSoftObjectPath& AssetPath = AssetPointer.ToSoftObjectPath(); // 获取资源路径

	if (AssetPath.IsValid()) // 检查资源路径是否有效
	{
		LoadedSubclass = AssetPointer.Get(); // 尝试获取子类
		if (!LoadedSubclass) // 如果子类未加载
		{
			LoadedSubclass = Cast<UClass>(SynchronousLoadAsset(AssetPath)); // 同步加载子类
			ensureAlwaysMsgf(LoadedSubclass, TEXT("加载资源类失败 [%s]"), *AssetPointer.ToString()); // 确保子类加载成功
		}

		if (LoadedSubclass && bKeepInMemory) // 如果子类已加载且需要保持在内存中
		{
			// 添加到已加载资源列表。
			Get().AddLoadedAsset(Cast<UObject>(LoadedSubclass)); // 添加到已加载资源集合
		}
	}

	return LoadedSubclass; // 返回加载的子类
}

#undef UE_API